package org.anachronos.jeep.probec;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;

import org.anachronos.jeep.probe.Enablement;
import org.anachronos.jeep.probe.OnMethod;
import org.anachronos.jeep.probec.model.ProbeEnablement;
import org.anachronos.jeep.probec.model.ProbeMethod;
import org.anachronos.jeep.probec.model.ProbeType;

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("org.anachronos.jeep.probe.Probe")
public class Probec extends AbstractProcessor {

	public Probec() {
		super();
	}

	@Override
	public boolean process(final Set<? extends TypeElement> annotations,
			final RoundEnvironment env) {
		final Iterator<? extends TypeElement> iterator = annotations.iterator();
		if (iterator.hasNext()) {
			final Set<? extends Element> probes = env.getElementsAnnotatedWith(iterator.next());
			final List<ProbeType> probeTypes = createProbeTypes(probes);
			generateProbes(probeTypes);
		}
		return false;
	}

	private void generateProbes(final List<ProbeType> probeTypes)  {
		final ClassAdapterGenerator generator = new ClassAdapterGenerator();
		for (final ProbeType probeType : probeTypes) {
			try {
				Writer writer = null;
				try {
					final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(probeType.getGenFullName());
					writer = sourceFile.openWriter();
					generator.generate(writer, probeType);
				} finally {
					if (writer != null) {
						writer.close();
					}
				}
			} catch (Exception e) {
				final StringWriter stringWriter = new StringWriter();
				final PrintWriter printWriter = new PrintWriter(stringWriter);
				e.printStackTrace(printWriter);
				processingEnv.getMessager().printMessage(Kind.ERROR, stringWriter.toString(), probeType.getTypeElement());
			} 
		}
	}

	private List<ProbeType> createProbeTypes(final Set<? extends Element> probes) {
		final List<ProbeType> probeTypes = new ArrayList<ProbeType>();
		final Set<TypeElement> types = ElementFilter.typesIn(probes);
		for (final TypeElement type : types) {
			final ProbeType probeType = ProbeType.create(type);
			probeTypes.add(probeType);
			final List<ExecutableElement> methods = ElementFilter.methodsIn(type.getEnclosedElements());
			final List<ProbeMethod> probeMethods = probeType.getMethods();
			for (final ExecutableElement method : methods) {
				if (method.getAnnotation(OnMethod.class) != null) {
					if (verify(method)) {
						final ProbeMethod probeMethod = ProbeMethod.create(method);
						probeMethods.add(probeMethod);
					}
				} else if (method.getAnnotation(Enablement.class) != null) {
					final ProbeEnablement enablement = new ProbeEnablement(method.getSimpleName().toString());
					probeType.setEnablement(enablement);
				}
			}
		}
		return probeTypes;
	}

	private boolean verify(final ExecutableElement method) {
		final OnMethod onMethod = method.getAnnotation(OnMethod.class);
		final boolean verifyMethodName = 
			checkRegexPattern(method, onMethod.methodName());
		final boolean verifyMethodDescriptor = 
			checkRegexPattern(method, onMethod.methodDescriptior());
		return verifyMethodName && verifyMethodDescriptor;
	}
	
	private boolean checkRegexPattern(final Element element, final String regexPattern) {
		try {
			Pattern.compile(regexPattern);
			return true;
		} catch (PatternSyntaxException e) {
			processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage(), element);
			return false;
		}
	}
}
